/*******************************************************************************
 * Copyright (c) 2001, 2002 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 *    David Corbin	- added MoveDeleteHook. 
 * 					- modify checkout, uncheckout to IClientApi, 
 *                    not PerforceUtil
 ******************************************************************************/
package net.sourceforge.perforce.core.internal;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import net.sourceforge.perforce.core.IP4TeamProvider;
import net.sourceforge.perforce.core.IProviderFactory;
import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.PerforceException;
import net.sourceforge.perforce.core.PerforceSynchronizer;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.api.IResolveResult;
import net.sourceforge.perforce.core.api.IWhereResult;
import net.sourceforge.perforce.core.internal.api.PerforceServerManager;
import net.sourceforge.perforce.core.internal.sync.SyncStatus;
import net.sourceforge.perforce.core.resources.LocalFile;
import net.sourceforge.perforce.core.resources.LocalFolder;
import net.sourceforge.perforce.core.resources.LocalResource;
import net.sourceforge.perforce.core.resources.PerforceRemoteSyncElement;
import net.sourceforge.perforce.core.resources.RemoteFile;
import net.sourceforge.perforce.core.resources.RemoteFolder;
import net.sourceforge.perforce.core.resources.RemoteResource;
import net.sourceforge.perforce.core.syncinfo.FolderSyncInfo;
import net.sourceforge.perforce.core.syncinfo.ResourceSyncInfo;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFileModificationValidator;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.team.IMoveDeleteHook;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;
import org.eclipse.team.core.sync.IRemoteResource;
import org.eclipse.team.core.sync.IRemoteSyncElement;

/**
  * @version $Revision: 191 $
  * @author  <a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  * @author  <a href="mailto:dcorbin@users.sourceforge.net">David Corbin</a>
  */
public class P4TeamProvider extends RepositoryProvider implements IP4TeamProvider
{
	// --------------------------------------------------------------------------- Private Variables
	
	/** Stores the associated PerforceServer instance. */	
	private IPerforceServer perforceServer;
	/** Stores the associated Eclipse Project. */
	private IProject project;
	/** Stores the IFileModificationValidator instance to be used. */
	private IFileModificationValidator fileModificationValidator;
	/** Stores the IMoveDeleteHook instance to be used. */
	private IMoveDeleteHook moveDeleteHook;
	
	// ----------------------------------------------------------------------- Public Static Methods
	
	/** Call this method to retrieve the P4TeamProvider that is responsible for
	  * the given resource. If the resource is not currently under control of a 
	  * P4TeamProvider, null is returned.
	  * 
	  * @param resource the resource.
	  * @return the responsible P4TeamProvider. 
	  */
	public static P4TeamProvider getProvider(IResource resource)
	{
		IProject project = resource.getProject();
		if (project == null)
		{
			return null;
		}
		
		RepositoryProvider provider = RepositoryProvider.getProvider(project);
		if (provider != null && provider.getID().equals(PerforceCorePlugin.getTypeId()))
		{
			return (P4TeamProvider) provider;
		}
		
		return null;
	}

	// ------------------------------------------------------------------------- Public Constructors

	/** Constructor for P4TeamProvider. */
	public P4TeamProvider()
	{
	}
	
	// ------------------------------------------------------------------------------ Public Methods
	
	/** Returns the associated PerforceServer instance.
	  *  
	  * @return the PerforceServer.
	  */	
	public IPerforceServer getPerforceServer()
	{
		Policy.assertNotNull(perforceServer);
		return perforceServer;
	}
	
	/** @see RepositoryProvider#getProviderType() */
	public String getID()
	{
		return PerforceCorePlugin.getTypeId();
	}

	/** @see RepositoryProvider#configureProject() */
	public void configureProject() 
		throws CoreException
	{
	}

	/** @see IProjectNature#deconfigure() */
	public void deconfigure() 
		throws CoreException
	{
	}

	/** @see IProjectNature#getProject() */
	public IProject getProject()
	{
		Policy.assertNotNull(project);
		return project;
	}

	/** @see IProjectNature#setProject(IProject) */
	public void setProject(IProject project)
	{
		Policy.assertNotNull(project);
		this.project = project;	
		
		try
		{
			PerforceServerManager manager = 
				(PerforceServerManager) PerforceCorePlugin.getServerManager();
			perforceServer = manager.getAssociatedPerforceServer(project);
		}
		catch (PerforceException e)
		{
			PerforceCorePlugin.log(e.getStatus());
		}
	}

	/** Call this method to open the given resources for edit.
	  * 
	  * @param resources array of resource that should be opned for edit.
	  * @param depth indicates the depth of the operation.
	  * @param monitor used for progress feedback.
	  * 
	  * @throws PerforceException in case of an exceptional error.
	  */
	public void checkout(IResource[] resources, int depth, IProgressMonitor monitor)
		throws PerforceException
	{
		IStatus status = openForEdit(resources, depth, monitor);
		if (!status.isOK())
		{
			throw new PerforceException(status);
		}
	}

	/** This method sync the given resources with the head revision as stored on the Perforce
	  * Server.
	  * 
	  * @param resources array of resources to sync.
	  * @param depth indicates the depth of the operation.
	  * @param progress used for progress feedback.
	  * 
	  * @throws TeamException indicates an exceptional error situation.
	  */
	public void get(IResource[] resources, int depth, IProgressMonitor progress)
		throws TeamException
	{
		MultiStatus status = 
			new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null); 

		progress.beginTask("Syncing ", resources.length); 		
		try
		{
			// we currently only support get of files, so this one is easy
			for (int i = 0; i < resources.length; i++)
			{
				Policy.assertTrue(resources[i].getType() == IResource.FILE);
				
				LocalFile localFile = 
					(LocalFile) LocalResource.createFromResource(resources[i]);
									
				progress.subTask(localFile.getSyncInfo().getDepotFileName());
				status.merge(localFile.sync("#head", progress));			
			}
		}
		finally
		{	
			try
			{	
				for (int i = 0, length = resources.length; i < length; i++)
				{
					resources[i].refreshLocal(IResource.DEPTH_ONE, progress);
				}
			}
			catch (CoreException e)
			{
				throw new TeamException(e.getStatus());
			}
			finally
			{				
				refreshResourceStates(resources, null);			
				progress.done();
			}
		}

		openDialogOnError(status);		
	}
	
	/** TODO:
	  * 
	  * @param resource the resource for whcih we need an IRemoteSyncElement.
	  * @param revision the revision we need.
	  * @param progress used for progress feedback.
	  * @return a RemoteSyncElement for the given resource#revision.
	  * 
	  * @throws TeamException in case of an exceptional error.
	  */
	public IRemoteSyncElement getRemoteSyncTree(
		IResource resource, String revision, IProgressMonitor progress)
		throws TeamException 
	{
		int type = resource.getType();
		if (type == IResource.FILE)
		{
			LocalFile localFile = (LocalFile) LocalResource.createFromResource(resource);
			
			String filename = localFile.getSyncInfo().getDepotFileName();
			IRemoteResource have = new RemoteFile(getPerforceServer(), filename, "#have");
			IRemoteResource head = new RemoteFile(getPerforceServer(), filename, "#head");
			
			return new PerforceRemoteSyncElement(false, localFile, have, head, null);
		}
		else 
		if (type == IResource.FOLDER || type == IResource.PROJECT)
		{
			LocalFolder localFolder = (LocalFolder) LocalResource.createFromResource(resource);

			String[] files = 
				getPerforceServer().sync(localFolder.getAbsolutePath() + "/...", true, progress);

			List syncStatus = new ArrayList();
			for (int i = 0, length = files.length; i < length; i++) 
			{
				syncStatus.add(new SyncStatus(files[i], SyncStatus.INCOMING));
			}
			
			IResolveResult[] resolveResults =
				getPerforceServer().resolve(localFolder.getAbsolutePath() + "/...", true);
			for (int i = 0, length = resolveResults.length; i < length; i++)
			{
				syncStatus.add(
					new SyncStatus(resolveResults[i].getLocalFilename(), SyncStatus.CONFLICTING));
			}
				
			IRemoteResource have = 
				new RemoteFolder(getPerforceServer(), null, localFolder.getFolderSync(), "#have");
			IRemoteResource head = 
				new RemoteFolder(getPerforceServer(), null, localFolder.getFolderSync(), "#head");
				
			Object[] syncStatusArray = syncStatus.toArray(new SyncStatus[0]);
			return new PerforceRemoteSyncElement(false, localFolder, have, head, syncStatusArray);
		}
		
		throw new RuntimeException();
	}

	/** TODO:
	  *
	  * @param resource the resource for whcih we need an RemoteResource.
	  * @param revision the revision we need.
	  * @param progress used for progress feedback.
	  * @return the RemoteResource.
	  * 
	  * @throws TeamException in case of an exception.
	  */
	public RemoteResource getRemoteTree(
		IResource resource, String revision, IProgressMonitor progress)
		throws TeamException
	{
		RemoteResource result = null;
		if (resource.getType() == IResource.FILE)
		{
			// p4 print file#have seems to be slower than p4 print file#haveRev ...
			if (revision.equals("#have"))
			{
				LocalResource file = LocalResource.createFromResource(resource);
				revision = "#" + file.getSyncInfo().getHaveRev(); 
			}
			
			String filename = 
				getPerforceServer().where(resource.getLocation().toOSString()).getDepotFilename();
			result = new RemoteFile(getPerforceServer(), filename, revision);
		}
		else
		{
			FolderSyncInfo folderSyncInfo =
				PerforceSynchronizer.getInstance().getFolderSync((IContainer) resource);
			result = new RemoteFolder(getPerforceServer(), null, folderSyncInfo, revision);			
		}
		
		return result;
	}

	/** @see IP4TeamProvider#hasRemote(IResource) */
	public boolean hasRemote(IResource resource)
	{
		LocalResource localResource = LocalResource.createFromResource(resource);		
		return localResource != null ? localResource.hasRemote() : false;
	}
	
	/** @see IP4TeamProvider#isIgnored(IResource) */
	public boolean isIgnored(IResource resource)
	{
		LocalResource localResource = LocalResource.createFromResource(resource);
		return localResource != null ? localResource.isIgnored() : false;
	}

	/** @see IP4TeamProvider#isCheckedOut(IResource) */
	public boolean isCheckedOut(IResource resource)
	{
		LocalResource localResource = LocalResource.createFromResource(resource);
		return localResource != null ? localResource.isCheckedOut() : false;			
	}
	
	/** @see IP4TeamProvider#isResolved(IResource) */
	public boolean isResolved(IResource resource)
	{
		LocalResource localResource = LocalResource.createFromResource(resource);
		return localResource != null ? localResource.isResolved() : false;
	}
	
	/** Call this method to refresh the team provider state of the given resources.
	  * 
	  * @param resources the resources to refresh.
	  * @param depth the depth of the operation.
	  * @param progress used for progress feedback.
	  * 
	  * @throws TeamException when something really bad happens.
	  */
	public void refreshState(IResource[] resources, int depth, IProgressMonitor progress)
		throws TeamException
	{
		progress = Policy.monitorFor(progress);
		progress.beginTask("", resources.length * 1000 + 5000);
		try
		{
			final List resourcesToRefresh = new ArrayList();
			for (int i = 0, length = resources.length; i < length; i++)
			{
				if (!resources[i].exists())
				{
					resources[i].refreshLocal(IResource.DEPTH_INFINITE, null);
				}
				
				if (resources[i].exists())
				{
					resources[i].accept(new IResourceVisitor()
					{
						public boolean visit(IResource resource) 
						{
							if (resource.getType() == IResource.FILE)
							{
								if (!isIgnored(resource))
								{
									resourcesToRefresh.add(resource);
								}
							}
							
							return !isIgnored(resource);
						}
	
					}, depth, false);
				}
				
				progress.worked(1000);
			}
			
			refreshResourceStates(
				(IResource[]) resourcesToRefresh.toArray(new IResource[0]),
				Policy.subMonitorFor(progress, 5000));			
		}
		catch (CoreException e)
		{
			throw new PerforceException(e.getStatus());
		}
		finally
		{
			progress.done();
		}
	}

	/** Sync the given resources with the perforce server.
	  * 
	  * @param resources the resources to sync.
	  * @param depth the depth of the operation.
	  * @param progress used for progress feedback.
	  * 
	  * @throws TeamException in case of an error.
	  */
	public void sync(IResource[] resources, int depth, IProgressMonitor progress)
		throws TeamException
	{				
		progress.beginTask("", resources.length * 1000 + 1000);         //$NON-NLS-1$
		
		List affectedFiles = new ArrayList();
		try
		{
			MultiStatus status =
				new MultiStatus(
					PerforceCorePlugin.ID, 
					MultiStatus.OK, 
					"Problem during 'p4 sync'.", 
					null);
				
			// we currently only support get of files, so this one is easy
			for (int i = 0; i < resources.length; i++)
			{
				IWhereResult whereResult = 
					getPerforceServer().where(resources[i].getLocation().toOSString());
				
				String syncString = whereResult.getClientFilename();
				progress.subTask("Syncing " + syncString);				
				
				if (resources[i].getType() != IResource.FILE)
				{
					switch (depth)
					{
						case IResource.DEPTH_ZERO:
							break;
							
						case IResource.DEPTH_ONE:
							syncString += "/*"; //$NON-NLS-1$
							break;
							
						case IResource.DEPTH_INFINITE: //$NON-NLS-1$
							syncString += "/...";
							break;
						
						default:
							Policy.assertTrue(false);
					}
				}
											
				status.merge(getPerforceServer().sync(syncString, "#head", affectedFiles, null));
				progress.worked(1000);							
			}		
			
			if (status.getSeverity() >= IStatus.WARNING)
			{
				throw new PerforceException(status);
			}	
		}
		finally
		{	
			resources = (IResource[]) affectedFiles.toArray(new IResource[affectedFiles.size()]);
			refreshResources(resources, progress);
		}
	}
		
	/** @see RepositoryProvider#getFileModificationValidator() */
	public synchronized IFileModificationValidator getFileModificationValidator()
	{
		if (fileModificationValidator == null)
		{
			IProviderFactory factory = PerforceCorePlugin.getPlugin().getProviderFactory();
			fileModificationValidator = factory.createFileModificationValidator(this);
		}
		
		return fileModificationValidator;
	}

	/** @see RepositoryProvider#getMoveDeleteHook() */
	public IMoveDeleteHook getMoveDeleteHook() 
	{
		if (moveDeleteHook == null)
		{
			moveDeleteHook = new MoveDeleteHook(this);
		}
		
		return moveDeleteHook;
	}
	
	// ------------------------------------------------------------ Public Methods (IP4TeamProvider)

	/** @see IP4RepositoryProvider#openForAdd(IResource[], int, IProgressMonitor) */
	public IStatus openForAdd(IResource[] resources, int depth, IProgressMonitor monitor)
		throws PerforceException
	{
		List affectedFilesList = new ArrayList();
	
		monitor = Policy.monitorFor(monitor);
		monitor.beginTask("Open for Add: ", resources.length * 1000 + 1000); 
		try
		{			
			MultiStatus result = 
				new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null);          //$NON-NLS-1$
				
			for (int i = 0, length = resources.length; i < length; i++)
			{
				monitor.subTask(resources[i].getLocation().toOSString());
				
				// Collect resource for add ....
				final List filesToAddList = new ArrayList();				
				resources[i].accept(new IResourceVisitor()
				{
					/** @see IResourceVisitor#visit(IResource) */
					public boolean visit(IResource resource) 
						throws CoreException
					{
						if (resource.getType() == IResource.FILE)
						{
							LocalResource localResource = 
								LocalResource.createFromResource(resource);
							if (!localResource.hasRemote())
							{
								filesToAddList.add(resource);
							}
						}
						
						return true;
					}
	
				}, depth, false);
								
				try
				{			
					String[] resourceNames = getResourceLocations(filesToAddList);					
					getPerforceServer().add(resourceNames, null);
				}
				catch (PerforceException e)
				{
					result.merge(e.getStatus());
				}
				
				affectedFilesList.addAll(filesToAddList);
				monitor.worked(1000);											
			}
			
			return result;
		}
		catch (CoreException e)
		{
			throw new PerforceException(e.getStatus());
		}
		finally
		{
			resources = 
				(IResource[]) affectedFilesList.toArray(new IResource[affectedFilesList.size()]);
			refreshResources(resources, monitor);
			
			monitor.done();
		}
	}
	
	/** @see IP4TeamProvider#openForDelete(IResource, int, IProgressMonitor) */
	public IStatus openForDelete(IResource[] resources, int depth, IProgressMonitor monitor)
		throws PerforceException 
	{
		List affectedFilesList = new ArrayList();

		monitor = Policy.monitorFor(monitor);
		monitor.beginTask("Open for Delete: ", resources.length * 1000 + 1000);		
		try
		{
			MultiStatus result = 
				new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null);          //$NON-NLS-1$
				
			for (int i = 0, length = resources.length; i < length; i++)
			{
				monitor.subTask(resources[i].getLocation().toOSString());

				// Collect resource to edit ....			
				final List filesToDelete = new ArrayList();	
				resources[i].accept(new IResourceVisitor()
				{
					public boolean visit(IResource resource) 
						throws CoreException
					{
						if (!hasRemote(resource) || isIgnored(resource))
						{
							return false;
						}
						
						if (resource.getType() == IResource.FILE)
						{
							filesToDelete.add(resource);
						}
						
						return true;
					}
				}, depth, false);
				
				try
				{			
					String[] resourceNames = getResourceLocations(filesToDelete);					
					getPerforceServer().delete(resourceNames);
				}
				catch (PerforceException e)
				{
					result.merge(e.getStatus());
				}
				
				affectedFilesList.addAll(filesToDelete);
				monitor.worked(1000);											
			}
			
			return result;					
		}
		catch (CoreException e)
		{
			throw new PerforceException(e.getStatus());
		}
		finally
		{		
			// We are deleting the resources, so we only refresh their parents			
			HashSet parents = new HashSet();
			for (Iterator it = affectedFilesList.iterator(); it.hasNext();)
			{
				IResource resource = (IResource) it.next();
				parents.add(resource.getParent());
			}	
			
			IResource[] refresh = (IResource[]) parents.toArray(new IResource[parents.size()]);
			refreshResources(refresh, monitor);
			
			monitor.done();
		}		
	}

	/** @see IP4TeamProvider#openForEdit(IResource, int, IProgressMonitor) */
	public IStatus openForEdit(IResource[] resources, int depth, IProgressMonitor monitor)
		throws PerforceException 
	{
		List affectedFilesList = new ArrayList();

		monitor = Policy.monitorFor(monitor);
		monitor.beginTask("Open For Edit: ", resources.length * 1000 + 1000);		
		try
		{
			MultiStatus result = 
				new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null);          //$NON-NLS-1$
				
			for (int i = 0, length = resources.length; i < length; i++)
			{
				monitor.subTask(resources[i].getLocation().toOSString());

				// Collect resource to edit ....			
				final List filesToEditList = new ArrayList();	
				resources[i].accept(new IResourceVisitor()
				{
					public boolean visit(IResource resource) 
						throws CoreException
					{
						LocalResource localResource = LocalResource.createFromResource(resource);
						if (!localResource.hasRemote())
						{
							return false;
						}
						
						if (resource.getType() == IResource.FILE)
						{
							filesToEditList.add(resource);
						}
						
						return true;
					}
				}, depth, false);
				
				try
				{			
					String[] resourceNames = getResourceLocations(filesToEditList);					
					getPerforceServer().edit(resourceNames, null);
				}
				catch (PerforceException e)
				{
					result.merge(e.getStatus());
				}
				
				affectedFilesList.addAll(filesToEditList);
				monitor.worked(1000);											
			}
			
			return result;					
		}	
		catch (CoreException e)
		{
			throw new PerforceException(e.getStatus());
		}
		finally
		{						
			resources = 
				(IResource[]) affectedFilesList.toArray(new IResource[affectedFilesList.size()]);
			refreshResources(resources, monitor);
			
			monitor.done();
		}		
	}
	
	/** @see IP4TeamProvider#revert(IResource, int, IProgressMonitor) */
	public IStatus revert(IResource[] resources, int depth, IProgressMonitor monitor)
		throws PerforceException 
	{
		List affectedResources = new ArrayList();
		
		monitor = Policy.monitorFor(monitor);
		monitor.beginTask("Revert: ", resources.length * 1000 + 1000); 
		try
		{			
			MultiStatus result = 
				new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null);          //$NON-NLS-1$
				
			for (int i = 0; i < resources.length;  i++)
			{
				final List toRevertList = new ArrayList();
				monitor.subTask(resources[i].getLocation().toOSString());
				
				// Collect resource to revert ....
				resources[i].accept(new IResourceVisitor()
				{
					public boolean visit(IResource resource) 
					{
						if (resource.getType() == IResource.FILE)
						{
							LocalResource localResource = 
								LocalResource.createFromResource(resource);
							if (localResource.hasRemote() && localResource.isCheckedOut())
							{
								toRevertList.add(resource);
							}
						}
						
						return true;
					}
				}, depth, false);

				try
				{								
					String[] resourceNames = getResourceLocations(toRevertList);
					getPerforceServer().revert(resourceNames, null);
				}
				catch (PerforceException e)
				{
					result.merge(e.getStatus());
				}
				
				affectedResources.addAll(toRevertList);
				monitor.worked(1000);
			}
					
			return result;			
		}
		catch (CoreException e)
		{
			throw new PerforceException(e.getStatus());
		}		
		finally
		{
			resources = 
				(IResource[]) affectedResources.toArray(new IResource[affectedResources.size()]);
			refreshResources(resources, monitor);
			
			monitor.done();
		}
	}
	
	/** @see IP4TeamProvider#markAsResolved(IResource, IProgressMonitor)
	 */
	public IStatus markAsResolved(IResource[] resources, IProgressMonitor monitor)
		throws PerforceException 
	{
		monitor = Policy.monitorFor(monitor);
		monitor.beginTask("Mark As Resolved: ", resources.length * 1000 + 1000); 
		try
		{			
			MultiStatus result = 
				new MultiStatus(PerforceCorePlugin.ID, IStatus.OK, "", null);          //$NON-NLS-1$
				
			for (int i = 0; i < resources.length; i++)
			{
				monitor.subTask(resources[i].getLocation().toOSString());
				
				getPerforceServer().resolve(resources[i].getLocation().toOSString(), false);
				
				monitor.worked(1000);
			}
			
			return result;
		}		
		finally
		{
			refreshResources(resources, monitor);
			
			monitor.done();
		}
	}

	// ----------------------------------------------------------------------------- Private Methods

	/** Called to retrieve an array of resource loactions from the List of IResources. 
	  *
	  * @param resourceList list of resources.+
	  * @return array of locations as string. 
	  */
	private String[] getResourceLocations(List resourceList) 
	{
		String[] result = new String[resourceList.size()];
		for (int j = 0; j < resourceList.size(); j++)
		{
			result[j] =	((IResource) resourceList.get(j)).getLocation().toOSString();
		}
		
		return result;
	}

	/** Called to refresh the given resources and their resource states. 
	  *
	  * @param resources the resources to refresh,
	  * @param monitor the progress monitor.
	  * @throws PerforceException in case of an unexpected error. 
	  */
	private void refreshResources(IResource[] resources, IProgressMonitor monitor)
		throws PerforceException 
	{
		try
		{	
			if (resources.length > 0)
			{
				IProgressMonitor subMonitor = Policy.subMonitorFor(monitor, 400);
				for (int i = 0, length = resources.length; i < length; i++)
				{											
					resources[i].refreshLocal(IResource.DEPTH_ONE, subMonitor);
				}
			}
			else
			{
				monitor.worked(400);
			}
		}
		catch (CoreException e)
		{
			throw new PerforceException(e.getStatus());
		}
		finally
		{	
			try
			{
				refreshState(resources, IResource.DEPTH_ONE, Policy.subMonitorFor(monitor, 600));
			}
			catch (TeamException e)
			{
				throw new PerforceException(e.getStatus());
			}
			
			monitor.done();
		}
	}	
	
	/** Returns a String that describes the size of the given array.
	  * 
	  * @param fileSet some objects.
	  * @return the String representation.
	  */
	private String fileSetDescriptor(Object[] fileSet)
	{
		if (fileSet.length == 1)
		{
			return fileSet[0].toString();
		}
		else
		if (fileSet.length == 0)
		{
			return "<none>";
		}
		else
		{
			return "<" + fileSet.length + " files>";
		}
	}	
	
	/** Provides wrapper for fileSetDescriptor(Object[]) taht can be used for Lists.
	  *  
	  * @param fileSet some objects as list.
	  * @return the String representation.
	  * 
	  * @see P4TeamProvider#fleSetDescriptor(Object[])
	  */ 
	private String fileSetDescriptor(List fileSet)
	{
		return fileSetDescriptor(fileSet.toArray());
	}
	
	/** Translates the provided status to an error dialog. 
	  * 
	  * @param status containing detailed information.
	  */
	private void openDialogOnError(final MultiStatus status)
	{
		if (status.getSeverity() != IStatus.ERROR)
		{
			return;
		}
			
		if (status.matches(IStatus.ERROR))
		{
			Display.getDefault().syncExec(new Runnable()
			{
				public void run()
				{
					ErrorDialog.openError(null, "Perforce Error", "A Perforce operation failed.", 
						status, IStatus.ERROR);
				}
			});
		}
	}		
	
	/** Broadcast changes to the specified resources.
	  * 
	  * @param resources array of changed resources.
	  * @param monitor used for progress feedback.
	  */ 	
	private void refreshResourceStates(IResource[] resources, IProgressMonitor monitor)
	{		
		PerforceSynchronizer synchronizer = PerforceCorePlugin.getSynchronizer();
		synchronizer.beginOperation(null);
		try
		{
			monitor = Policy.monitorFor(monitor);
			monitor.beginTask("", resources.length * 300);			
			
			monitor.subTask("Collecting synchronization info...");
			Map resourceStateMap = new HashMap();
			
			int j = 0;
			for (int i = 0; i < resources.length; i++)
			{
				if (resources[i].getType() == IResource.FILE)
				{
					if (++j % 50 == 0)
					{
						monitor.subTask("Collecting synchronization info... [" + j + "]");
					}
					
					ResourceSyncInfo[] syncInfos =
						getPerforceServer().fstat(resources[i].getLocation().toOSString(), null);
					if (syncInfos.length == 1 && !syncInfos[0].isDeleted())
					{
						resourceStateMap.put(resources[i], syncInfos[0]);
					}
					else
					{
						resourceStateMap.put(resources[i], null);
					}
				}
				
				monitor.worked(100);
			}

			monitor.subTask("Refreshing cache...");
			for (Iterator it = resourceStateMap.entrySet().iterator(); it.hasNext();)
			{
				Map.Entry entry = (Map.Entry) it.next();
				
				IResource resource = (IResource) entry.getKey();			
				ResourceSyncInfo syncInfo = (ResourceSyncInfo) entry.getValue();
				
				if (syncInfo == null)
				{
					synchronizer.deleteResourceSync(resource);
				}
				else
				{
					synchronizer.setResourceSync(resource, syncInfo);
				}
				
				monitor.worked(100);
			}
			
			Map folderStateMap = addAllParents(resourceStateMap, monitor);
			for (Iterator it = folderStateMap.entrySet().iterator(); it.hasNext();)
			{
				Map.Entry entry = (Map.Entry) it.next();
				
				IContainer resource = (IContainer) entry.getKey();
				FolderSyncInfo syncInfo = (FolderSyncInfo) entry.getValue();
				
				if (syncInfo == null)
				{
					synchronizer.deleteFolderSync(resource);
				}
				else
				{
					synchronizer.setFolderSync(resource, syncInfo);
				}
			}			
		}
		catch (CoreException e)
		{
			PerforceCorePlugin.log(e.getStatus());
		}
		catch (PerforceException e)
		{
			PerforceCorePlugin.log(e.getStatus());
		}
		finally
		{
			try
			{
				synchronizer.endOperation(monitor);
			}
			catch (PerforceException e)
			{
				PerforceCorePlugin.log(e.getStatus());
			}
			
			monitor.done();
		}
	}
	
	/** Adds all parent resource (and their FolderSyncInfo) to the given map and returns the 
	  * resulting new map.
	  * 
	  * @param resourceStateMap map containing resources.
	  * @param monitor for progress feedback. 
	  * @return the new map.
	  * 
	  * @throws CoreException in case of a core error.
	  * @throws PerforceException in case of an unexpected error.
	  */
	private Map addAllParents(Map resourceStateMap, IProgressMonitor monitor)
		throws PerforceException, CoreException
	{
		Map result = new HashMap();
		for (Iterator it = resourceStateMap.entrySet().iterator(); it.hasNext();)
		{
			Map.Entry entry = (Map.Entry) it.next();
			
			IResource resource = (IResource) entry.getKey();
			ResourceSyncInfo syncInfo = (ResourceSyncInfo) entry.getValue();
			if (syncInfo != null)
			{
				String depotFilename = syncInfo.getDepotFileName();
				
				IContainer parent = resource.getParent();
				while (
					parent != null && 
					parent.getType() != IResource.ROOT && 
					!result.containsKey(parent))
				{
					depotFilename = depotFilename.substring(0, depotFilename.lastIndexOf('/'));
					
					FolderSyncInfo folderSyncInfo = new FolderSyncInfo(depotFilename);
					result.put(parent, folderSyncInfo);
					parent = parent.getParent();
				}
			}			
			else
			{
				IContainer parent = resource.getParent();
				while (
					parent != null && 
					parent.getType() != IResource.ROOT && 
					!result.containsKey(parent))
				{	
					PerforceSynchronizer synchronizer = PerforceSynchronizer.getInstance();			
					IResource[] children = parent.members();
					
					FolderSyncInfo folderSyncInfo = null;
					for (int i = 0; i < children.length && folderSyncInfo == null; i++)
					{
						IResource child = children[i];
																
						if (child.getType() == IResource.FILE)
						{
							ResourceSyncInfo info = synchronizer.getResourceSync(child);
							if (info != null)
							{
								String depotFilename = info.getDepotFileName();
								depotFilename = 								
									depotFilename.substring(0, depotFilename.lastIndexOf('/'));
								folderSyncInfo = new FolderSyncInfo(depotFilename);
							}
						}
						else
						if (child.getType() == IResource.FOLDER)
						{
							FolderSyncInfo info = null;
							if (result.containsKey(child))
							{
								info = (FolderSyncInfo) result.get(child);
							}
							else 
							{
								info = synchronizer.getFolderSync((IContainer) child);
							}
							
							if (info != null)
							{
								String depotFilename = info.getDepotFileName();
								depotFilename = 
									depotFilename.substring(0, depotFilename.lastIndexOf('/'));
								folderSyncInfo = new FolderSyncInfo(depotFilename);
							}
						}
					}
					
					result.put(parent, folderSyncInfo);
					parent = parent.getParent();
				}
			}
			monitor.worked(100);
		}
		
		//result.putAll(resourceStateMap);
		return result;
	}	
}
